package pt.compflow;

import java.util.Iterator;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.restlet.Component;
import org.restlet.data.Protocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pt.compflow.activity.gateway.GatewayExecutor;
import pt.compflow.activity.task.InputFinder;
import pt.compflow.activity.task.InputFinderImpl;
import pt.compflow.activity.task.TaskController;
import pt.compflow.activity.task.TaskControllerImpl;
import pt.compflow.activity.workflow.WorkflowController;
import pt.compflow.activity.workflow.WorkflowControllerImpl;
import pt.compflow.client.EngineApplication;
import pt.compflow.flowontology.model.FLOWO;
import pt.compflow.flowontology.model.FlowOntologyModel;
import pt.compflow.interfaces.InterfaceManager;
import pt.compflow.interfaces.event.EventInterface;
import pt.compflow.interfaces.task.TaskInterface;
import pt.compflow.job.JobController;
import pt.compflow.job.JobControllerImpl;
import pt.compflow.job.JobTemplateManager;
import pt.compflow.storage.SDBTripleStoreConnection;
import pt.compflow.storage.SDBTripleStoreConnectionFactory;
import pt.compflow.storage.TripleStoreConnection;
import pt.compflow.storage.TripleStoreConnectionFactory;
import pt.compflow.tools.ClassMapManager;
import pt.compflow.tools.EngineThreadPoolExecutor;
import pt.compflow.tools.OntModelHelper;
import pt.compflow.tools.OntModelHelperManager;

import com.hp.hpl.jena.ontology.OntDocumentManager;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Resource;

/**
 * Context class that serves as wrapper to several engine manager and controller
 * instances that define the context of the engine/system
 *
 */
public class Context {

	private static final Logger LOG = LoggerFactory.getLogger(Context.class);
	
	/**
	 * Create a new default context that includes the instantiation of a
	 * task and workflow controllers
	 * @param store: the triple store where data must be read/written
	 * @param server
	 * @return
	 * @throws Exception 
	 */
	public static Context getDefaultContext() throws Exception {
		return getDefaultContext("engine.properties");
	}
	
	public static Context getDefaultContext(String propertiesFile) throws Exception {
		
		Context context = new Context();
		
		// Engine Properties
		Properties engineProperties = new Properties();
		engineProperties.load(Context.class.getResourceAsStream("/" + propertiesFile));
		context.setEngineProperties(engineProperties);
		
		// Engine OntModelHelper
		OntModel engineOntModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_DL_MEM);
		if(engineProperties.containsKey("ontologies")) {
			String[] ontologies = engineProperties.getProperty("ontologies").split(";");
	
			// read all ontologies specified in the TripleStore config file to the ont model
			for (int i = 0; i < ontologies.length; i++) {
				engineOntModel.read(ontologies[i], null);
			}
		} else {
			engineOntModel.read("file:FlowOntologyV2.owl", null);
		}
		OntModelHelper helper = new OntModelHelper(engineOntModel);
		context.setEngineOntModelHelper(helper);	
		
		// Store Connection Factory
		context.setStoreConnectionFactory(new SDBTripleStoreConnectionFactory(engineProperties));
		
		// Thread Pools
		context.setExecutorService(new EngineThreadPoolExecutor(15, 30));
		context.setScheduledExecutorService(Executors.newScheduledThreadPool(5));

		// Task and Event Interface Managers
		InterfaceManager<EventInterface> eventInterfaceManager = new InterfaceManager<EventInterface>();
		eventInterfaceManager.loadFromN3(Context.class.getResourceAsStream("/event_interfaces.n3"));
		context.setEventInterfaceManager(eventInterfaceManager);
		
		InterfaceManager<TaskInterface> taskInterfaceManager = new InterfaceManager<TaskInterface>();
		taskInterfaceManager.loadFromN3(Context.class.getResourceAsStream("/task_interfaces.n3"));
		context.setTaskInterfaceManager(taskInterfaceManager);
	
		// Gateway Manager
		ClassMapManager<GatewayExecutor> gatewayExecutorManager = new ClassMapManager<GatewayExecutor>();
		gatewayExecutorManager.loadFromN3(Context.class.getResourceAsStream("/gateways.n3"));
		context.setGatewayExecutorManager(gatewayExecutorManager);
		
		// OntModelHelper Manager
		OntModelHelperManager ontModelHelperManager = new OntModelHelperManager();
		context.setOntModelHelperManager(ontModelHelperManager);

		// Instantiator (instantiates activity-definitions)
		Instantiator instantiator = new InstantiatorImpl();
		context.setInstantiator(instantiator);
		
		// Input Finder (loads the input of a Task/Assignment)
		InputFinder taskInputFinder = new InputFinderImpl();
		context.setInputFinder(taskInputFinder);

		// Task Controller
		TaskController taskController = new TaskControllerImpl(context);
		context.setTaskController(taskController);
		
		// Workflow Controller
		WorkflowController wc = new WorkflowControllerImpl(context);
		context.setWorkflowController(wc);
		
		// Job Controller
		JobController jc = new JobControllerImpl(context);
		context.setJobController(jc);
		
		// Job Template Manager
		JobTemplateManager jtm = new JobTemplateManager();
		context.setJobTemplateManager(jtm);
		
		// Restlet Component
		EngineApplication engineApplication = new EngineApplication(context);
		context.setEngineApplication(engineApplication);
		
	    Component component = new Component();  
	    component.getContext().getParameters().add("idleCheckInterval", "500");
	    component.getContext().getParameters().add("idleTimeout", "60000");
	    component.getContext().getParameters().add("socketTimeout", "60000");
	    component.getContext().getParameters().add("type", "2");	// apparently this solves the thread hang with max CPU thing
	    component.getServers().add(Protocol.HTTP, Integer.parseInt(engineProperties.getProperty("enginePort", "8000")));  
	    component.getDefaultHost().attachDefault(engineApplication);  
	    context.setRestletInterface(component);
		
		return context;
	}
	
	protected Properties engineProperties;
	protected TripleStoreConnectionFactory storeConnectionFactory;
	protected OntModelHelper engineOntModelHelper;

	protected InterfaceManager<TaskInterface> taskInterfaceManager;
	protected InterfaceManager<EventInterface> eventInterfaceManager;

	protected ClassMapManager<GatewayExecutor> gatewayExecutorManager;
	
	protected JobTemplateManager jobTemplateManager;
	protected OntModelHelperManager ontModelHelperManager;
	
	protected JobController jobController;
	protected WorkflowController workflowController;
	protected TaskController taskController;
	
	protected InputFinder inputFinder;
	protected Instantiator instantiator;
	
	protected ExecutorService executorService;
	protected ScheduledExecutorService scheduledExecutorService;
	
	protected EngineApplication engineApplication;
	protected Component restletInterface;

	public Context() {
	}
	
	public void start() throws Exception {
		LOG.info("Starting workflow engine from context...");
		
		OntDocumentManager.getInstance().setProcessImports(true);
		
		SDBTripleStoreConnection store = (SDBTripleStoreConnection) getStoreConnection();
		store.openStore();
						
		// init interfaces
		FlowOntologyModel model = store.connectModel();
		
		Iterator<pt.compflow.flowontology.model.TaskInterface> tiIt = model.listTaskInterfaces();
		while(tiIt.hasNext()) {
			pt.compflow.flowontology.model.TaskInterface ti = tiIt.next();
			Resource tiClass = engineOntModelHelper.getDirectLeafType(ti, FLOWO.TaskInterface.getURI());
			if(tiClass == null) {
				LOG.warn("TaskInterface class not found in the engine ontology model for " + ti.getURI() + ". Please make sure that an ontology in the \"ontologies\" parameter of the configuration file contains the class of this interface.");
			} else {
				taskInterfaceManager.get(ti, tiClass, engineOntModelHelper.getOntModel(), this, store);
			}
		}
		
		Iterator<pt.compflow.flowontology.model.EventInterface> eiIt = model.listEventInterfaces();
		while(eiIt.hasNext()) {
			pt.compflow.flowontology.model.EventInterface ei = eiIt.next();
			Resource eiClass = engineOntModelHelper.getDirectLeafType(ei, FLOWO.EventInterface.getURI());
			if(eiClass == null) {
				LOG.warn("EventInterface class not found in the engine ontology model for " + ei.getURI() + ". Please make sure that an ontology in the \"ontologies\" parameter of the configuration file contains the class of this interface.");
			} else {
				eventInterfaceManager.get(ei, eiClass, engineOntModelHelper.getOntModel(), this, store);
			}
		}
		
		store.closeStore();
		
		taskController.start();
		workflowController.start();
		jobController.start();
		
		restletInterface.start();
	}
		
	public void stop() throws Exception {
		LOG.info("Stopping workflow engine and destroying context...");
		
		LOG.debug("Stopping restlet interface component...");
		restletInterface.stop();
		
		executorService.shutdown();
		scheduledExecutorService.shutdown();
		LOG.debug("Waiting for ThreadPool to shutdown...");
		executorService.awaitTermination(60000, TimeUnit.MILLISECONDS);
		scheduledExecutorService.awaitTermination(60000, TimeUnit.MILLISECONDS);

		LOG.debug("Shutting down controllers...");
		taskController.stop();
		workflowController.stop();
		jobController.stop();
		
		LOG.debug("Shutting down interfaces...");
		SDBTripleStoreConnection store = (SDBTripleStoreConnection) getStoreConnection();
		store.openStore();
		
		FlowOntologyModel model = store.connectModel();
		
		Iterator<pt.compflow.flowontology.model.TaskInterface> tiIt = model.listTaskInterfaces();
		while(tiIt.hasNext()) {
			pt.compflow.flowontology.model.TaskInterface ti = tiIt.next();
			Resource tiClass = engineOntModelHelper.getDirectLeafType(ti, FLOWO.TaskInterface.getURI());
			if(tiClass == null) {
				LOG.warn("TaskInterface class not found in the engine ontology model for " + ti.getURI() + ". Please make sure that an ontology in the \"ontologies\" parameter of the configuration file contains the class of this interface.");
			} else {
				TaskInterface i = (TaskInterface) taskInterfaceManager.get(ti, tiClass, engineOntModelHelper.getOntModel(), this, store);
				i.stop(this, store);
			}
		}
		
		Iterator<pt.compflow.flowontology.model.EventInterface> eiIt = model.listEventInterfaces();
		while(eiIt.hasNext()) {
			pt.compflow.flowontology.model.EventInterface ei = eiIt.next();
			Resource eiClass = engineOntModelHelper.getDirectLeafType(ei, FLOWO.EventInterface.getURI());
			if(eiClass == null) {
				LOG.warn("EventInterface class not found in the engine ontology model for " + ei.getURI() + ". Please make sure that an ontology in the \"ontologies\" parameter of the configuration file contains the class of this interface.");
			} else {
				EventInterface i = (EventInterface) eventInterfaceManager.get(ei, eiClass, engineOntModelHelper.getOntModel(), this, store);
				i.stop(this, store);
			}
		}
		
		LOG.debug("Cleaning up interface managers...");
		taskInterfaceManager.cleanup(this, store);
		eventInterfaceManager.cleanup(this, store);
		
		store.closeStore();
				
		LOG.debug("Cleaning up OntModelHelper manager...");
		ontModelHelperManager.cleanup();
		
		LOG.info("Destroy complete!");
	}
	
	public Properties getEngineProperties() {
		return engineProperties;
	}

	public void setEngineProperties(Properties engineProperties) {
		this.engineProperties = engineProperties;
	}

	public OntModelHelper getEngineOntModelHelper() {
		return engineOntModelHelper;
	}

	public void setEngineOntModelHelper(OntModelHelper engineOntModelHelper) {
		this.engineOntModelHelper = engineOntModelHelper;
	}

	public Component getRestletInterface() {
		return restletInterface;
	}

	public void setRestletInterface(Component restletInterface) {
		this.restletInterface = restletInterface;
	}

	public ClassMapManager<GatewayExecutor> getGatewayExecutorManager() {
		return gatewayExecutorManager;
	}

	public Instantiator getInstantiator() {
		return instantiator;
	}

	public WorkflowController getWorkflowController() {
		return workflowController;
	}

	public TaskController getTaskController() {
		return taskController;
	}

	public void setGatewayExecutorManager(ClassMapManager<GatewayExecutor> gatewayExecutorManager) {
		this.gatewayExecutorManager = gatewayExecutorManager;
	}

	public void setInstantiator(Instantiator instantiator) {
		this.instantiator = instantiator;
	}

	public void setWorkflowController(WorkflowController workflowController) {
		this.workflowController = workflowController;
	}

	public void setTaskController(TaskController taskController) {
		this.taskController = taskController;
	}

	public TripleStoreConnection getStoreConnection() {
		return storeConnectionFactory.getTripleStoreConnection();
	}
	
	public TripleStoreConnectionFactory getStoreConnectionFactory() {
		return storeConnectionFactory;
	}

	public void setStoreConnectionFactory(TripleStoreConnectionFactory storeConnectionFactory) {
		this.storeConnectionFactory = storeConnectionFactory;
	}

	public InterfaceManager<TaskInterface> getTaskInterfaceManager() {
		return taskInterfaceManager;
	}

	public void setTaskInterfaceManager(InterfaceManager<TaskInterface> taskInterfaceManager) {
		this.taskInterfaceManager = taskInterfaceManager;
	}

	public InterfaceManager<EventInterface> getEventInterfaceManager() {
		return eventInterfaceManager;
	}

	public void setEventInterfaceManager(InterfaceManager<EventInterface> eventInterfaceManager) {
		this.eventInterfaceManager = eventInterfaceManager;
	}

	public JobController getJobController() {
		return jobController;
	}

	public void setJobController(JobController jobController) {
		this.jobController = jobController;
	}

	public ExecutorService getExecutorService() {
		return executorService;
	}

	public void setExecutorService(ExecutorService executorService) {
		this.executorService = executorService;
	}

	public ScheduledExecutorService getScheduledExecutorService() {
		return scheduledExecutorService;
	}

	public void setScheduledExecutorService(ScheduledExecutorService scheduledExecutorService) {
		this.scheduledExecutorService = scheduledExecutorService;
	}

	public EngineApplication getEngineApplication() {
		return engineApplication;
	}

	public void setEngineApplication(EngineApplication engineApplication) {
		this.engineApplication = engineApplication;
	}

	public JobTemplateManager getJobTemplateManager() {
		return jobTemplateManager;
	}

	public void setJobTemplateManager(JobTemplateManager jobTemplateManager) {
		this.jobTemplateManager = jobTemplateManager;
	}

	public InputFinder getInputFinder() {
		return inputFinder;
	}

	public void setInputFinder(InputFinder inputFinder) {
		this.inputFinder = inputFinder;
	}

	public OntModelHelperManager getOntModelHelperManager() {
		return ontModelHelperManager;
	}

	public void setOntModelHelperManager(OntModelHelperManager ontModelHelperManager) {
		this.ontModelHelperManager = ontModelHelperManager;
	}

}
